/*
 **  PTYSession.m
 **
 **  Copyright (c) 2002, 2003
 **
 **  Author: Fabian, Ujwal S. Sathyam
 **
 **  Project: iTerm
 **
 **  Description: Implements the model class for a terminal session.
 **
 **  This program is free software; you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation; either version 2 of the License, or
 **  (at your option) any later version.
 **
 **  This program is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with this program; if not, write to the Free Software
 **  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#import "iTerm.h"
#import "PTYSession.h"
#import "PTYTask.h"
#import "PTYTextView.h"
#import "VT100Screen.h"
#import "VT100Terminal.h"
#import "ITPreferences.h"
#import "ITTerminal.h"
#import "NSString+Communique.h"
#import "PTYScrollView.h"
#import "VT100Typesetter.h"
#import "ITSessionDesc.h"
#import "NSStringUtils.h"

#include <unistd.h>

#define kSessionTimerInterval 2
#define kBlinkTimerInterval .3  // want a fast blink

@interface PTYSession (Private)
- (void)screenUpdate;
- (void)delayedScreenUpdate;
- (void)startBlinkTimer:(BOOL)start;
- (void)startSessionTimer:(BOOL)start;
@end

@implementation PTYSession

static NSString *TERM_ENVNAME = @"TERM";
static NSString *PWD_ENVNAME = @"PWD";
static NSString *PWD_ENVVALUE = @"~";

// init/dealloc
- (id) init
{
    if((self = [super init]) == nil)
        return (nil);

    _iIdleCount=0;
    oIdleCount=1000;

    waiting=antiIdle=EXIT=NO;
    
    if (normalStateAttribute == nil) 
    {
        normalStateAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
            [NSColor blackColor],NSForegroundColorAttributeName,nil] retain];
        
        chosenStateAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
            [NSColor blackColor],NSForegroundColorAttributeName,nil] retain];
        
        idleStateAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
            [NSColor redColor],NSForegroundColorAttributeName,nil] retain];
        
        newOutputStateAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
            [NSColor purpleColor],NSForegroundColorAttributeName,nil] retain];
        
        deadStateAttribute = [[NSDictionary dictionaryWithObjectsAndKeys:
            [NSColor grayColor],NSForegroundColorAttributeName,nil] retain];
    }
    
    return (self);
}

- (void) dealloc
{
    _performDelayedUpdate = NO;

    [TERM_VALUE release];
    [_scrollView release];
    [_name release];
        
    [normalStateAttribute release];
    normalStateAttribute = nil;
    [chosenStateAttribute release];
    chosenStateAttribute = nil;
    [idleStateAttribute release];
    idleStateAttribute = nil;
    [newOutputStateAttribute release];
    newOutputStateAttribute = nil;
        
    [super dealloc];    
}

// Session specific methods
- (void)initScreen: (NSRect) aRect
{	
    // Allocate screen, shell, and terminal objects
    _scrollView = [[PTYScrollView alloc] initWithFrame: aRect];
	aRect=NSMakeRect(0, 0, [_scrollView contentSize].width, [_scrollView contentSize].height);
    
    if ([[ITPreferences sharedInstance] enforceCharacterAlignment]) {
        NSLayoutManager* layoutManager = [[[NSLayoutManager alloc] init] autorelease];
		NSTextContainer *aTextContainer;
        
        textStorage = [[NSTextStorage alloc] init];
        [textStorage addLayoutManager:layoutManager];
        
        aTextContainer = [[[NSTextContainer alloc] initWithContainerSize: NSMakeSize([_scrollView contentSize].width, 1e7)] autorelease];
        [layoutManager addTextContainer:aTextContainer];
        
        VT100Typesetter *aTypesetter = [[[VT100Typesetter alloc] init] autorelease];
        [aTypesetter setScreen: _screen];
        [layoutManager setTypesetter: aTypesetter];
        
        _textView = [[[PTYTextView alloc] initWithFrame: NSMakeRect(0, 0, [_scrollView contentSize].width, [_scrollView contentSize].height) textContainer: aTextContainer] autorelease];
        [aTextContainer setWidthTracksTextView:YES];
		[aTextContainer setHeightTracksTextView:NO];
        
		[_textView setMaxSize:NSMakeSize(1e7, 1e7)];
		[_textView setHorizontallyResizable:NO];
		[_textView setVerticallyResizable:YES];
    }
    else
		_textView = [[[PTYTextView alloc] initWithFrame: aRect] autorelease];
  
    [_textView setDrawsBackground:NO];
    [_textView setEditable:YES];
    [_textView setSelectable:YES];
    [_textView setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable];
	
	[_textView setTextContainerInset:NSMakeSize(4,8)];
    
    [_textView setDelegate: self];
    [_textView setAntiAlias: [[ITPreferences sharedInstance] antiAlias]];
    [_scrollView setDocumentView:_textView];
    [_scrollView setDocumentCursor: [NSCursor arrowCursor]];
    [_scrollView setDelegate: self];
	
	// adjust for frame border
	aRect = [_textView frame];
	aRect.size.height -= (8*2);
	aRect.size.width -= (4*2);	
	
    _shell = [[PTYTask alloc] init];
    _terminal = [[VT100Terminal alloc] init];
	_screen = [[VT100Screen alloc] initWithFrame: aRect];
	
    NSParameterAssert(_shell != nil && _screen != nil && _terminal != nil && _scrollView != nil);
	
    NSLog(@"%@", _screen);
	
    [_screen setSession:self];
    [self setName:@"Shell"];    
	
    // Allocate a scrollview and add to the tabview
		
	[_screen setTextView:_textView];
	
    //[self setSizeFromFrame:aRect];	
	
    ai_code=0;
    antiIdle = NO;
    _refreshed = NO;

    //[_tabViewItem setLabelAttributes: chosenStateAttribute];
}

- (NSSize) getSizeFromFrame:(NSRect)frame
{	
	//NSLog (@"%@", frame);
	frame.size.height -= (8*2);
	frame.size.width -= (4*2);
   return [VT100Screen screenSizeInFrame:frame font: [_screen tallerFont]];
}

- (void) setSizeFromFrame:(NSRect)frame;
{
    NSSize termSize = [self getSizeFromFrame:frame];
    
	NSLog (@"%.0f:%.0f", termSize.width, termSize.height);
	
    [_screen resizeWidth:termSize.width height:termSize.height];
    [_shell setWidth:termSize.width height:termSize.height];	
}

- (void)scrollViewResized: (PTYScrollView *) scrollView;
{
    NSRect frame = [[_scrollView contentView] frame];
	
    [self setSizeFromFrame:frame];
	
    [_textView scrollEnd];
}

- (void)startSession:(NSString*)title blinkCursor:(BOOL)blinkCursor;
{
    if (title)
        [self setName:title];
    
    [_screen setBlinkingCursor:blinkCursor];
    
    if (blinkCursor)
        [self startBlinkTimer:YES];
    
    [self startSessionTimer:YES];
}

- (BOOL)isActiveSession
{    
    if (_tabViewItem)
    {
        // is this tab the current tab
        if ([[_tabViewItem tabView] selectedTabViewItem] == _tabViewItem)
        {
            NSWindow *window = [[_tabViewItem tabView] window];
            
            if (window && [window isKeyWindow] && (_textView == [window firstResponder]))
                return YES;
        }
    }
    
    return NO;
}

- (void)startProgram:(NSString *)program
	   arguments:(NSArray *)prog_argv
	 environment:(NSDictionary *)prog_env
{
    NSString *path = program;
    NSMutableArray *argv = [NSMutableArray arrayWithArray:prog_argv];
    NSMutableDictionary *env = [NSMutableDictionary dictionaryWithDictionary:prog_env];

    if ([env objectForKey:TERM_ENVNAME] == nil)
        [env setObject:TERM_VALUE forKey:TERM_ENVNAME];

    if ([env objectForKey:PWD_ENVNAME] == nil)
        [env setObject:[PWD_ENVVALUE stringByExpandingTildeInPath] forKey:PWD_ENVNAME];

    [_shell setDelegate:self];
	[_shell launchWithPath:path
				arguments:argv
				environment:env
						width:[_screen width]
					   height:[_screen height]];

}

- (void)terminate
{
    [_shell sendSignal: SIGHUP];

    [_tabViewItem release];
    _tabViewItem = nil;

    [_shell setDelegate:nil];
    [_shell release];
    _shell = nil;

    [_terminal release];
    _terminal = nil;
    
    // make sure we set the firstResponder to nil if this text view is first responder, otherwise it can bomb
    if ([[_textView window] firstResponder] == _textView)
        [[_textView window] makeFirstResponder:nil];
    
    [_textView setDataSource: nil];
    [_textView setDelegate: nil];
    [_textView removeFromSuperview];
    _textView = nil;
    
    [textStorage release];
    textStorage = nil;
    
    [_screen release];
    _screen   = nil;
    
    // stops and deallocs the timer
    [self startSessionTimer:NO];
    [self startBlinkTimer:NO];
    
    parent = nil;
}

- (void) foundString:(NSString*)string {
	if ([_delegate respondsToSelector:@selector(foundString:)])
		[_delegate foundString:string];
}

- (void)setDelegate:(id)object
{
    [_delegate autorelease];
    _delegate = [object retain];
}

- (id)delegate:(id)object
{
    return _delegate;
}

- (void)readTask:(NSData *)data
{
    VT100TCC token;
	
	if (data == nil)
        return;
    
    [_terminal putStreamData:data];
    
    if ([_screen screenIsLocked]) 
        return;
    
    if (_refreshed == NO)
    {
        _refreshed = YES;
        
        if ([[_tabViewItem tabView] selectedTabViewItem] != _tabViewItem)
            [_tabViewItem setLabelAttributes: newOutputStateAttribute];
    }
    
    if (_terminal)
    {
        for (;;)
        {
            token = [_terminal getNextToken];
            
            if ((token.type != VT100CC_NULL) && (token.type != VT100_WAIT))
            {
                if (token.type != VT100_SKIP)
                    [_screen putToken:token];
            }
            else
                break;
        }
        
        // if just a few characters (like typing) do an immediate update
        if ([data length] < 50)
            [self screenUpdate];
        else
            [self delayedScreenUpdate];
    }
    
    oIdleCount = 0;
    
    if (token.type == VT100_NOTSUPPORT)
		NSLog(@"%s(%d):not support token", __FILE__ , __LINE__);
}

- (NSData *)readData {
	return [_shell readData];
};

- (void)brokenPipe
{
    [_shell sendSignal:SIGKILL];
    [_shell stop];
    EXIT = YES;

    // stop the timer? why here?
    [self startSessionTimer:NO];
    
    if (autoClose)
        [parent closeSession:self];
    else 
    {
        [self setName:[NSString stringWithFormat:@"[%@]",[self name]]];
        //[_tabViewItem setLabelAttributes: deadStateAttribute];
    }
}

// PTYTextView
- (void)keyDown:(NSEvent *)event
{
    unsigned char *send_str = NULL;
    size_t send_strlen = 0;
    int send_chr = -1;
    int send_pchr = -1;

    unsigned int modflag;
    unsigned short keycode;
    NSString *keystr;
    NSString *unmodkeystr;
    unichar unicode;
    
    modflag = [event modifierFlags];
    keycode = [event keyCode];
    keystr  = [event characters];
    unmodkeystr = [event charactersIgnoringModifiers];
    unicode = [keystr length]>0?[keystr characterAtIndex:0]:0;

    _iIdleCount=0;

    // Clear the bell
    [self setBell: NO];

    if ( (modflag & NSFunctionKeyMask) && [[ITPreferences sharedInstance] macintoshNavKeys] && ((unicode == NSPageUpFunctionKey) || (unicode == NSPageDownFunctionKey) || (unicode == NSHomeFunctionKey) || (unicode == NSEndFunctionKey)) ) {
        modflag ^= NSShiftKeyMask; // Toggle meaning of shift key for
    }

    // Check if we are navigating through sessions or scrolling
    if ((modflag & NSFunctionKeyMask) && ((modflag & NSCommandKeyMask) || (modflag & NSShiftKeyMask)))
    {
	// command/shift + function key's
	switch (unicode)
	{
	    case NSDeleteFunctionKey:
		break;
	    case NSPageUpFunctionKey:
                [_textView scrollPageUp: self];
		break;
	    case NSPageDownFunctionKey:
                [_textView scrollPageDown: self];
		break;
	    case NSHomeFunctionKey:
                [_textView scrollHome];
		break;
	    case NSEndFunctionKey:
                [_textView scrollEnd];
		break;
	    case NSClearLineFunctionKey:
		if(modflag & NSCommandKeyMask)
		    [_terminal toggleNumLock];
		break;
            case NSUpArrowFunctionKey:
                if ((modflag & NSShiftKeyMask) && (modflag & NSCommandKeyMask))
                    [_textView scrollPageUp: self];
                else
                    [_textView scrollLineUp: self];
                break;
            case NSDownArrowFunctionKey:
                if ((modflag & NSShiftKeyMask) && (modflag & NSCommandKeyMask))
                    [_textView scrollPageDown: self];
                else
                    [_textView scrollLineDown: self];
                break;
                
	    default:
		if (NSF1FunctionKey<=unicode&&unicode<=NSF35FunctionKey)
		    [parent selectSessionAtIndex:unicode-NSF1FunctionKey];                    
		break;
	}
    }
    else if((modflag & NSAlternateKeyMask) && (unicode == NSDeleteCharacter))
	[self setRemapDeleteKey: ![self remapDeleteKey]];
    else 
    {
	if (modflag & NSFunctionKeyMask)
        {
	    NSData *data = nil;

	    switch(unicode) 
            {
                case NSUpArrowFunctionKey: data = [_terminal keyArrowUp:modflag]; break;
		case NSDownArrowFunctionKey: data = [_terminal keyArrowDown:modflag]; break;
		case NSLeftArrowFunctionKey: data = [_terminal keyArrowLeft:modflag]; break;
		case NSRightArrowFunctionKey: data = [_terminal keyArrowRight:modflag]; break;

		case NSInsertFunctionKey:
		    // case NSHelpFunctionKey:
		    data = [_terminal keyInsert]; break;
		case NSDeleteFunctionKey:
		    data = [_terminal keyDelete]; break;
		case NSHomeFunctionKey: data = [_terminal keyHome]; break;
		case NSEndFunctionKey: data = [_terminal keyEnd]; break;
		case NSPageUpFunctionKey: data = [_terminal keyPageUp]; break;
		case NSPageDownFunctionKey: data = [_terminal keyPageDown]; break;

		case NSPrintScreenFunctionKey:
		    break;
		case NSScrollLockFunctionKey:
		case NSPauseFunctionKey:
		    break;
		case NSClearLineFunctionKey:
		    if(![_terminal numLock] || [_terminal keypadMode])
			data = [_terminal keyPFn: 1];
		    break;
	    }

            if (NSF1FunctionKey<=unicode&&unicode<=NSF35FunctionKey)
                data = [_terminal keyFunction:unicode-NSF1FunctionKey+1];

	    if (data != nil) {
			send_str = (unsigned char *)[data bytes];
			send_strlen = [data length];
	    }
	}
	else if ([[ITPreferences sharedInstance] optionKeyMode] != OPT_NORMAL && (modflag & NSAlternateKeyMask))
	{
	    NSData *keydat = ((modflag & NSControlKeyMask) && unicode>0)?
	    [keystr dataUsingEncoding:NSUTF8StringEncoding]:
	    [unmodkeystr dataUsingEncoding:NSUTF8StringEncoding];
	    // META combination
	    if (keydat != nil) {
		send_str = (unsigned char *)[keydat bytes];
		send_strlen = [keydat length];
	    }
            if ([[ITPreferences sharedInstance] optionKeyMode] == OPT_ESC) {
		send_pchr = '\e';
//                send_chr=unmodkeystr;
            }
	    else if ([[ITPreferences sharedInstance] optionKeyMode] == OPT_META && send_str != NULL) 
            {
		int i;
		for (i = 0; i < send_strlen; ++i)
		    send_str[i] |= 0x80;
	    }
	}
	else 
        {
	    int max = [keystr length];
	    NSData *data;

            if (max!=1||[keystr characterAtIndex:0] > 0x7f)
                data = [keystr dataUsingEncoding:[_terminal encoding]];
            else
                data = [keystr dataUsingEncoding:NSUTF8StringEncoding];

	    // Check if we are in keypad mode
	    if((modflag & NSNumericPadKeyMask) && (![_terminal numLock] || [_terminal keypadMode]))
	    {
		switch (unicode)
		{
		    case '=':
			data = [_terminal keyPFn: 2];;
			break;
		    case '/':
			data = [_terminal keyPFn: 3];
			break;
		    case '*':
			data = [_terminal keyPFn: 4];
			break;
		    default:
			data = [_terminal keypadData: unicode keystr: keystr];
			break;
		}
	    }

	    // Check if we want to remap the delete key to backspace
	    if((unicode == NSDeleteCharacter) && [self remapDeleteKey])
		data = [_terminal keyBackspace];

	    if (data != nil ) {
		send_str = (unsigned char *)[data bytes];
		send_strlen = [data length];
	    }

	    if (modflag & NSNumericPadKeyMask &&
		send_strlen == 1 &&
		send_str[0] == 0x03)
	    {
		send_str = "\012";  // NumericPad Entry -> 0x0a
		send_strlen = 1;
	    }
	    if (modflag & NSControlKeyMask &&
		send_strlen == 1 &&
		send_str[0] == '|')
	    {
		send_str = "\034"; // ^\
		send_strlen = 1;
	    }
	}

	// Make sure we scroll down to the end
	[_textView scrollEnd];

	if (EXIT == NO ) 
        {
	    if (send_pchr >= 0) {
			char c = send_pchr;
			[_shell writeTask:[NSData dataWithBytes:&c length:1]];
	    }
	    if (send_chr >= 0) {
			char c = send_chr;
			[_shell writeTask:[NSData dataWithBytes:&c length:1]];
	    }
			if (send_str != NULL) {
			[_shell writeTask:[NSData dataWithBytes:send_str length:send_strlen]];
	    }

	    // scroll to the end
		[_textView scrollEnd];
	    //[_screen updateScreen];
	}
    }
}

- (BOOL)willHandleEvent: (NSEvent *) theEvent
{
    // Handle the option-click event
    return (([theEvent type] == NSLeftMouseDown) &&
	    ([theEvent modifierFlags] & NSAlternateKeyMask));       
}

- (void)handleEvent: (NSEvent *) theEvent
{
    // We handle option-click to position the cursor...
    if(([theEvent type] == NSLeftMouseDown) &&
       ([theEvent modifierFlags] & NSAlternateKeyMask))
	[self handleOptionClick: theEvent];
}

- (void) handleOptionClick: (NSEvent *) theEvent
{
    // Here we will attempt to position the cursor to the mouse-click

    NSPoint locationInWindow, locationInTextView, locationInScrollView;
    NSSize fontSize;
    int x, y;

    locationInWindow = [theEvent locationInWindow];
    locationInTextView = [_textView convertPoint: locationInWindow fromView: nil];
    locationInScrollView = [_scrollView convertPoint: locationInWindow fromView: nil];

    fontSize = [_screen characterSize];
    x = (locationInTextView.x - fontSize.width)/fontSize.width + 1;
    y = locationInScrollView.y/fontSize.height + 1;

    // NSLog(@"loc_x = %f; loc_y = %f", locationInTextView.x, locationInScrollView.y);
    // NSLog(@"font width = %f, font height = %f", fontSize.width, fontSize.height);
    // NSLog(@"x = %d; y = %d", x, y);


    if(x == [_screen cursorX] && y == [_screen cursorY])
	return;

    NSData *data;
    int i;
    // now move the ` up or down
    for(i = 0; i < abs(y - [_screen cursorY]); i++)
    {
	if(y < [_screen cursorY])
            data = [_terminal keyArrowUp:0];
	else
            data = [_terminal keyArrowDown:0];
	[_shell writeTask:[NSData dataWithBytes:[data bytes] length:[data length]]];
    }
    // now move the cursor left or right    
    for(i = 0; i < abs(x - [_screen cursorX]); i++)
    {
	if(x < [_screen cursorX])
	    data = [_terminal keyArrowLeft:0];
	else
	    data = [_terminal keyArrowRight:0];
	[_shell writeTask:[NSData dataWithBytes:[data bytes] length:[data length]]];
    }
    
    // trigger an update of the display.
    [_screen updateScreen];
}

- (void)insertText:(NSString *)string
{
    NSData *data;
    NSMutableString *mstring;
    int i, max;

//    NSLog(@"insertText: %@",string);
    mstring = [NSMutableString stringWithString:string];
    max = [string length];
    for(i=0; i<max; i++) {
        if ([mstring characterAtIndex:i] == 0xa5) {
            [mstring replaceCharactersInRange:NSMakeRange(i, 1) withString:@"\\"];
        }
    }

    //if([_terminal encoding] != NSUTF8StringEncoding) {
    //    data = [mstring dataUsingEncoding:[_terminal encoding]
    //                allowLossyConversion:YES];
    //} else {
    //    char *fs_str = (char *)[mstring fileSystemRepresentation];
    //    data = [NSData dataWithBytes:fs_str length:strlen(fs_str)];
    //}
    
    data = [mstring dataUsingEncoding:[_terminal encoding]
		 allowLossyConversion:YES];

    if (data != nil) 
		[_shell writeTask:data];
}

- (void)insertNewline:(id)sender
{
    [self insertText:@"\n"];
}

- (void)insertTab:(id)sender
{
    [self insertText:@"\t"];
}

- (void)moveUp:(id)sender
{
    [_shell writeTask:[_terminal keyArrowUp:0]];
}

- (void)moveDown:(id)sender
{
    [_shell writeTask:[_terminal keyArrowDown:0]];
}

- (void)moveLeft:(id)sender
{
    [_shell writeTask:[_terminal keyArrowLeft:0]];
}

- (void)moveRight:(id)sender
{
    [_shell writeTask:[_terminal keyArrowRight:0]];
}

- (void)pageUp:(id)sender
{
    [_shell writeTask:[_terminal keyPageUp]];
}

- (void)pageDown:(id)sender
{
    [_shell writeTask:[_terminal keyPageDown]];
}

- (void)paste:(id)sender
{
    NSPasteboard *board;
    NSString *str;

    board = [NSPasteboard generalPasteboard];
    NSParameterAssert(board != nil );
    str = [board stringForType:NSStringPboardType];
	
    [_textView scrollEnd];
    [self pasteString: str];
}

- (void) pasteString: (NSString *) aString
{
    if ([aString length] > 0)
    {
		NSMutableString *replaced = [NSStringUtils replaceString:aString from:@"\n" to:@"\r"];
        NSData *strdata = [replaced dataUsingEncoding:[_terminal encoding] allowLossyConversion:YES];
        if (strdata != nil)
		{
			// Do this in a new thread since we do not want to block the read code.
			[NSThread detachNewThreadSelector:@selector(writeTask:) toTarget:_shell withObject:strdata];		
		}
    }
    else
		NSBeep();
}

- (void)deleteBackward:(id)sender
{
    unsigned char p = 0x08;	// Ctrl+H

    [_shell writeTask:[NSData dataWithBytes:&p length:1]];
}

- (void)deleteForward:(id)sender
{
    unsigned char p = 0x7F;	// DEL

    [_shell writeTask:[NSData dataWithBytes:&p length:1]];
}

- (void)sessionTimer:(NSTimer*)sender
{
    oIdleCount++; 
    
    if (antiIdle) 
    {
        _iIdleCount++; 

        if (_iIdleCount >= (120 / kSessionTimerInterval))  // every two minutes
        {
            [_shell writeTask:[NSData dataWithBytes:&ai_code length:1]];
            _iIdleCount = 0;
        }
    }
    
    // if we are not the selected tab, update
    if ([[_tabViewItem tabView] selectedTabViewItem] != _tabViewItem) 
        [self setLabelAttribute];
}

- (void)blinkTimer:(NSTimer*)sender;
{
    [_screen blink];
}

- (void) setBell
{
    [self setBell:YES];
}

- (void) setBell: (BOOL) flag
{
    [_tabViewItem setBell:flag];
}

- (void)setPreferencesFromSessionDesc:(ITSessionDesc *)desc
{
    NSColor *colorTable[2][8];
    int i;
    
    // set the font
    [_textView  setFont:[desc font]];
    [_screen  setFont:[desc font] nafont:[desc nonASCIIFont]];
    
    [self setForegroundColor:[desc foreColor]];
    [self setBackgroundColor:[desc backColor]];
    [self setSelectionColor:[desc selectionColor]];
    [self setBoldColor:[desc boldColor]];
    colorTable[0][0] = [desc ANSIBlackColor];
    colorTable[0][1] = [desc ANSIRedColor];
    colorTable[0][2] = [desc ANSIGreenColor];
    colorTable[0][3] = [desc ANSIYellowColor];
    colorTable[0][4] = [desc ANSIBlueColor];
    colorTable[0][5] = [desc ANSIMagentaColor];
    colorTable[0][6] = [desc ANSICyanColor];
    colorTable[0][7] = [desc ANSIWhiteColor];
    colorTable[1][0] = [desc ANSIHilightBlackColor];
    colorTable[1][1] = [desc ANSIHilightRedColor];
    colorTable[1][2] = [desc ANSIHilightGreenColor];
    colorTable[1][3] = [desc ANSIHilightYellowColor];
    colorTable[1][4] = [desc ANSIHilightBlueColor];
    colorTable[1][5] = [desc ANSIHilightMagentaColor];
    colorTable[1][6] = [desc ANSIHilightCyanColor];
    colorTable[1][7] = [desc ANSIHilightWhiteColor];
    
    for(i=0;i<8;i++) {
        [self setColorTable:i highLight:NO color:colorTable[0][i]];
        [self setColorTable:i highLight:YES color:colorTable[1][i]];
    }
    
    // set up the rest of the preferences
    [self setEncoding:[desc encoding]];
    [self setTERM_VALUE:[desc terminalType]];
    [self setAntiCode:[desc idleKeepAliveCharacterCode]];
    [self setAntiIdle:[desc isIdleKeepAliveEnabled]];
    [self setAutoClose:[desc closeSessionWhenEnds]];
    [self setDoubleWidth:[desc doubleWidthForNonASCIICharacters]];
    [self setRemapDeleteKey:[desc deleteKeySendsBackspace]];
}

- (ITTerminal *) parent
{
    return (parent);
}

- (void) setParent: (ITTerminal *) theParent
{
    parent = theParent; // don't retain parent. parent retains self.
}

- (PTYTabViewItem *) _tabViewItem
{
    return (_tabViewItem);
}

- (void) setTabViewItem: (PTYTabViewItem *) theTabViewItem
{
    [_tabViewItem autorelease];
    _tabViewItem = [theTabViewItem retain];
}

- (NSString *) uniqueID
{
    return ([self tty]);
}

- (void) setUniqueID: (NSString *)uniqueID
{
    NSLog(@"Not allowed to set unique ID");
}

- (NSString *) name
{
    return _name;
}

- (void)setName:(NSString *)theName
{    
    [_name autorelease];
    _name = [theName retain];
    
    if ([theName length] > 20)
    {
        NSMutableString *aMutableString = [[NSMutableString alloc] initWithString: [theName substringWithRange: NSMakeRange(0, 17)]];
        [aMutableString appendString: @"..."];
        [_tabViewItem setLabel: aMutableString];
        [self setBell: NO];
        [aMutableString release];
    }
    else 
    {
        [_tabViewItem setLabel: theName];
        [self setBell: NO];
    }
}

- (PTYTask *)shell
{
    return (_shell);
}

- (void) setSHELL: (PTYTask *) theSHELL
{
    [_shell autorelease];
    _shell = [theSHELL retain];
}

- (VT100Terminal *) TERMINAL
{
    return (_terminal);
}

- (void) setTERMINAL: (VT100Terminal *) theTERMINAL
{
    [_terminal autorelease];
    _terminal = [theTERMINAL retain];
}

- (NSString *) TERM_VALUE
{
    return (TERM_VALUE);
}

- (void) setTERM_VALUE: (NSString *) theTERM_VALUE
{
    [TERM_VALUE autorelease];
    TERM_VALUE = [theTERM_VALUE retain];
}

- (VT100Screen *) SCREEN
{
    return (_screen);
}

- (void) setSCREEN: (VT100Screen *) theSCREEN
{
    [_screen autorelease];
    _screen = [theSCREEN retain];
}

- (PTYTextView *) TEXTVIEW
{
    return (_textView);
}

- (NSScrollView *)scrollView
{
    return _scrollView;
}

- (void)setEncoding:(NSStringEncoding)encoding
{
    [_terminal setEncoding:encoding];
}

- (NSString *) tty
{
    return ([_shell tty]);
}

- (int) number
{
    return ([[_tabViewItem tabView] indexOfTabViewItem: _tabViewItem]);
}

- (NSColor *) foregroundColor
{
    return ([_terminal defaultFGColor]);
}

- (void)setForegroundColor:(NSColor*) color
{
    if(color == nil)
        return;

    [_textView setTextColor: color];
    
    if(([_terminal defaultFGColor] != color) || 
        ([[_terminal defaultFGColor] alphaComponent] != [color alphaComponent]))
    {
        // Change the fg color for future stuff
        [_terminal setFGColor: color];
    }
}

- (NSColor *) backgroundColor
{
    return ([_terminal defaultBGColor]);
}

- (void)setBackgroundColor:(NSColor*) color
{
    if(color == nil)
        return;
      
    if(([_terminal defaultBGColor] != color) || 
        ([[_terminal defaultBGColor] alphaComponent] != [color alphaComponent]))
    {
        // Change the bg color for future stuff
        [_terminal setBGColor: color];
    }
    
    [_scrollView setBackgroundColor: color];
}

- (NSColor *) boldColor
{
    return ([_terminal defaultBoldColor]);
}

- (void)setBoldColor:(NSColor*) color
{
    [_terminal setBoldColor: color];
}

- (NSColor *) selectionColor
{
    return ([_textView selectionColor]);
}

- (void) setSelectionColor: (NSColor *) color
{
    [_textView setSelectionColor: color];
}

// Changes transparency

- (float) transparency
{
    return (100*(1 - [[_terminal defaultBGColor] alphaComponent]));
}

- (void)setTransparency:(float)transparency
{
    NSColor *newcolor;
    
    newcolor = [[_terminal defaultBGColor] colorWithAlphaComponent:(1 - transparency/100)];
    if (newcolor != nil && newcolor != [_terminal defaultBGColor])
        [self setBackgroundColor: newcolor];
}

- (void) setColorTable:(int) index highLight:(BOOL)hili color:(NSColor *) c
{
    [_terminal setColorTable:index highLight:hili color:c];
}

- (BOOL) antiIdle
{
    return antiIdle;
}

- (int) antiCode
{
    return ai_code;
}

- (void) setAntiIdle:(BOOL)set
{
    antiIdle = set;
    
    if (antiIdle) 
        _iIdleCount=0;
}

- (void) setAntiCode:(int)code
{
    ai_code=code;
}

- (BOOL) autoClose
{
    return autoClose;
}

- (void) setAutoClose:(BOOL)set
{
    autoClose=set;
}

- (BOOL) doubleWidth
{
    return doubleWidth;
}

- (void) setDoubleWidth:(BOOL)set
{
    doubleWidth=set;
}

- (void)logStart
{
    NSSavePanel *panel;
    int sts;

    panel = [NSSavePanel savePanel];
    sts = [panel runModalForDirectory:NSHomeDirectory() file:@""];
    if (sts == NSOKButton) {
        BOOL logsts = [_shell loggingStartWithPath:[panel filename]];
        if (logsts == NO)
            NSBeep();
    }
}

- (void)logStop
{
    [_shell loggingStop];
}

- (void)clearBuffer
{
    char formFeed = 0x0c; // ^L
    [_terminal cleanStream];

    [_screen clearBuffer];
    // tell the shell to clear the screen
    [_shell writeTask:[NSData dataWithBytes:&formFeed length:1]];
}

- (void)clearScrollbackBuffer
{
    [_screen clearScrollbackBuffer];
}

- (BOOL)refreshed
{
    return _refreshed;
}

- (void) resetStatus;
{
    waiting = _refreshed = NO;
}

- (BOOL)exited
{
    return EXIT;
}

- (BOOL) remapDeleteKey
{
    return (remapDeleteKey);
}

- (void) setRemapDeleteKey: (BOOL) flag
{
    remapDeleteKey = flag;
}

- (NSTextStorage *) textStorage
{
    return ([_textView textStorage]);
}

- (void)setLabelAttribute
{
    if ([self exited])
        [_tabViewItem setLabelAttributes: deadStateAttribute];
    else if ([[_tabViewItem tabView] selectedTabViewItem] != _tabViewItem) 
    {
        if (oIdleCount>20 && !waiting) 
        {
            waiting=YES;
            if (_refreshed)
                [_tabViewItem setLabelAttributes: idleStateAttribute];
            else
                [_tabViewItem setLabelAttributes: normalStateAttribute];
        }
        else if (waiting && oIdleCount<=20)
        {
            waiting=NO;
            [_tabViewItem setLabelAttributes: newOutputStateAttribute];
        }
    }
    else
        [_tabViewItem setLabelAttributes: chosenStateAttribute];
    
    [self setBell:NO];
}

@end

@implementation PTYSession (Private)

- (void)screenUpdate;
{
    [_screen updateScreen];
    
    // If the user has not scrolled up, move to the end
    if (([[_textView enclosingScrollView] documentVisibleRect].origin.y + [[_textView enclosingScrollView] documentVisibleRect].size.height) == ([_textView frame].origin.y + [_textView frame].size.height))
        [_textView scrollEnd];    
}

- (void)delayedScreenUpdate;
{
    if (!_performDelayedUpdate)
    {
        _performDelayedUpdate = YES;
        [self performSelector:@selector(doDelayedScreenUpdate:) withObject:nil afterDelay:.2];
    }
}    

- (void)doDelayedScreenUpdate:(id)unused;
{
    if (_performDelayedUpdate)
    {        
        [self screenUpdate];
        _performDelayedUpdate = NO;
    }
}    

- (void)startSessionTimer:(BOOL)start;
{
    if (start)
    {
        if (!_timer)
        {
            _timer = [[NSTimer scheduledTimerWithTimeInterval:kSessionTimerInterval
                                                            target:self
                                                          selector:@selector(sessionTimer:)
                                                          userInfo:nil
                                                           repeats:YES] retain];
        }
    }
    else
    {
        if (_timer)
        {
            [_timer invalidate];
            [_timer release];
            _timer = nil;
        }
    }
}

- (void)startBlinkTimer:(BOOL)start;
{
    if (start)
    {
        if (!_blinkTimer)
        {
            _blinkTimer = [[NSTimer scheduledTimerWithTimeInterval:kBlinkTimerInterval
                                                            target:self
                                                          selector:@selector(blinkTimer:)
                                                          userInfo:nil
                                                           repeats:YES] retain];
        }
    }
    else
    {
        if (_blinkTimer)
        {
            [_blinkTimer invalidate];
            [_blinkTimer release];
            _blinkTimer = nil;
        }
    }
}

@end

